#!/usr/bin/env python
# -*- coding: utf-8 -*-
# KernelCheck
# Copyright (C) Team KernelCheck 2009 <master.kernel.contact@gmail.com>
#
# KernelCheck is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# KernelCheck is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

# Set global variables
SCROLL_ON_OUTPUT = False

# Import needed modules
import gtk.glade
import os
import sys
import re
import signal
import socket
import urllib2
import tempfile
import gobject
import pango
import platform
from subprocess import *

# Custom modules
from KernelCheck.constants import *
from KernelCheck.KCDebug import KCDebug

# Try to import gtk
try:
  import gtk
except:
  print >> sys.stderr, "Вам необходимо установить python gtk bindings(пакет python-gtk2)"
  sys.exit(1)

# Try to import vte
try:
  import vte
except:
  error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
    'You need to install python bindings for libvte')
  error.run()
  sys.exit (1)

# Trap interrupting signals and exit safely
def signal_handler(signal, frame):
    print
    print
    print "Signal caught. Exiting safely."
    try:
        gtk.main_quit()
    except:
        sys.exit(1)
    print

signal.signal(signal.SIGHUP, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGQUIT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

class KernelCheck:

    def __init__(self):

        # Check if there is an internet connection
        if self.net_test():
            print "Есть активное интернет-соединение."
        else:
            error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, 'You are not connected to the internet. Certain features may be disabled.')
            error.run()
            print "Connection refused."
            sys.exit(1)

        # Get the Glade file and retrieve needed widgets
        self.widgets = gtk.glade.XML(KCHECK_UI + "terminal.glade")
        self.ProgressBar = self.widgets.get_widget("progressbar")
        self.lblMessage = self.widgets.get_widget("lblMessage")
        self.btnQuit = self.widgets.get_widget("btnQuit")

        # Let the program know a kernel is not building
        self.buildlock = False
        self.thread_running = False
        self.error = False

        # Add a terminal
        self.Terminal = vte.Terminal()
        self.widgets.get_widget("TerminalFrame").add(self.Terminal)
        font = pango.FontDescription("monospace normal 8")
        self.Terminal.set_font(font)
        self.Terminal.show()
        # If the program exits, stop the loop
        self.Terminal.connect ("child-exited", self.run_command_done_callback)
        self.Terminal.connect('eof', self.run_command_done_callback)
        # Scroll on output, set lines equivalent to 1.0 MB
        self.Terminal.set_scroll_on_output(SCROLL_ON_OUTPUT)
        self.Terminal.set_scrollback_lines(1580)

        # Define and connect the event dictionary
        signalDic = { "on_btnQuit_clicked" : self.destroy_window,
                      "on_MainWindow_delete_event" : self.returnt }
        self.widgets.signal_autoconnect(signalDic)

        #Test data retrieval
        self.build("call")

    def str2tuple(self, s):
        """Convert tuple-like strings to real tuples.
        eg '(1,2,3,4)' -> (1, 2, 3, 4)
        """
        eval(s)
        return s

    # Traps the destroy signal so the program doesn't close before the message box is shown
    def returnt(self, widget, arg):
        self.destroy_window("argument")
        return True

    # Procedure for the network connection
    def net_test(self):
        print "\nпроверяем наличие активного интернет-соединения..."
        try:
            internet = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            internet.connect(("www.google.com", 80))
            return True
        except socket.error:
            return False

    # Make sure the user cannot accidentally close the window when the kernel is building
    def closeallowed(self, sensitivity):
        self.btnQuit.set_sensitive(sensitivity)

    # Run a command in the terminal
    def run_command(self, command_string):
        '''''run_command runs the command_string in the terminal. This
        function will only return when self.thread_running is set to
        True, this is done by run_command_done_callback'''
        self.thread_running = True
        command = command_string.split(' ')
        pid = self.Terminal.fork_command(command=command[0], argv=command, directory=os.getcwd())
        while self.thread_running:
            gtk.main_iteration()

    # Command finished
    def run_command_done_callback(self, terminal):
        '''''When called this function sets the thread as done allowing
        the run_command function to exit'''
        self.thread_running = False
        return

    # When an error occurs, reset everything
    def errorreset(self):
        error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, 'Произошла ошибка. Проверьте пожалуйста вывод терминала для выяснения деталей.')
        error.connect("response", self.close)
        error.run()
        self.ProgressBar.set_fraction(0.0)
        self.lblMessage.set_text('Произошла ошибка.')
        self.error = True
        self.buildlock = False
        self.closeallowed(True)

    def close(self, w, res):
        if res == gtk.RESPONSE_CLOSE:
            gtk.main_quit()

    # Check if there was an error with the last command ran in the terminal
    def checkerror(self):
        try:
            file = open('/tmp/stage-failed.tmp', 'r')
        except IOError:
            pass
        else:
            file.close()
            os.remove('/tmp/stage-failed.tmp')
            self.errorreset()

    ## Function to verify the NVidia version string
    def is_nv_version_number(self, data):
        ## Check if our data matches the pattern x.x or x.x.x where x = number
        if re.match('^\d+[.]\d+$', data) or re.match('^\d+[.]\d+[.]\d+$', data):
            return True
        else:
            return False

    # Fuction to get latest nvidia version for a given architecture
    def nv_get_latest(self):
        # Try to capture nVidia unix drivers page
        try:
            buffer = urllib2.urlopen("http://www.nvidia.com/object/unix.html").read()

        except IOError, e:
            if hasattr(e, 'reason'):
                ## Output error to terminal window if we have one open
                print 'Не получилось установить соединение с www.nvidia.com'
                print 'Причина: ' + str(e.reason)
                sys.exit(1)

            elif hasattr(e, 'code'):
                ## Output error to terminal window if we have one open
                print 'сервер www.nvidia.com  не смог обработать запрос.'
                print 'Код ошибки: ' + str(e.code)
                sys.exit(1)

        ## Get the current operating architecture
        arch = platform.architecture()[0]

        ## Initialise our variables
        nv_driver_version = ''
        nv_driver_url = ''
        nv_driver_file = ''

        ## If our architecture is 64bit
        if arch == "64bit":
            ## Find the latest nVidia driver version
            nv_driver_version = re.findall('Последняя версия драйвера  nVidia: <A href="/object/linux_display_amd64_(.*).html">', buffer)[0]

            ## Make sure our version number is valid
            if self.is_nv_version_number(nv_driver_version):
                print "Получаем " + arch + "последнюю версию  nVidia драйвера" + nv_driver_version + " информация..."
                nv_driver_url = "http://us.download.nvidia.com/XFree86/Linux-x86_64/" + nv_driver_version + "/NVIDIA-Linux-x86_64-" + nv_driver_version + "-pkg2.run"
                nv_driver_file = "NVIDIA-Linux-x86_64-" + nv_driver_version + "-pkg2.run"
            else:
                print "не можем найти соответствующую версию" + arch + " nVidia драйвера ..."
                nv_driver_version = None
                nv_driver_url = None
                nv_driver_file = None

        ## If our architecture is 32bit
        elif arch == "32bit":
            ## Find the latest NVidia version number
            nv_driver_version = re.findall('Последняя  версия: <A href="/object/linux_display_ia32_(.*).html">', buffer)[0]

            ## Make sure our version number is valid
            if self.is_nv_version_number(nv_driver_version):
                print "Получение " + arch + " аерсии  nVidia драйвера  " + nv_driver_version + " info..."
                nv_driver_url = "http://us.download.nvidia.com/XFree86/Linux-x86/" + nv_driver_version + "/NVIDIA-Linux-x86-" + nv_driver_version + "-pkg1.run"
                nv_driver_file = "NVIDIA-Linux-x86-" + nv_driver_version + "-pkg1.run"
            else:
                print "Не можем найти правильной версии  " + arch + " nVidia драйвера ..."
                nv_driver_version = None
                nv_driver_url = None
                nv_driver_file = None

        ## If our architecture is something obscure
        else:
            print "Извините!"
            print arch + " Данная архитектура не поддерживается программой KernelCheck."
            nv_driver_version = None
            nv_driver_url = None
            nv_driver_file = None

        return nv_driver_version, nv_driver_url, nv_driver_file


    # From the old build_kernel file -- gets the temporary location for the data file
    def get_temp_path(self, data):
        print "-- Процесс инициализируется --"
        print

        # Get data from tuple
        stable = data[0]
        stable_url = data[1]
        patch = data[2]
        patch_url = data[3]
        patchtype = data[4]
        config = data[5]
        alsa = data[6]
        optimize = data[7]
        reconfigure = data[8]
        nvidia = data[9]
        fglrx = data[10]
        installpackage = data[11]
        custom = data[12]
        cleanup = data[13]
        ccjobs = data[14]

        try:
            t_path = tempfile.mktemp()
            ttt = os.open(t_path, os.O_CREAT | os.O_RDWR)
        # O_RDWR allows read & write
        except IOError:
            print 'Ошибка:  не можем создать временный файл.'
            sys.exit(0)

        os.write(ttt, str(stable) + '\n')
        os.write(ttt, str(stable_url) + '\n')
        os.write(ttt, str(patch) + '\n')
        os.write(ttt, str(patch_url) + '\n')
        os.write(ttt, str(patchtype) + '\n')
        os.write(ttt, str(config) + '\n')
        os.write(ttt, str(alsa) + '\n')
        os.write(ttt, str(optimize) + '\n')
        os.write(ttt, str(reconfigure) + '\n')
        os.write(ttt, str(nvidia) + '\n')
        os.write(ttt, str(fglrx) + '\n')
        os.write(ttt, str(installpackage) + '\n')
        os.write(ttt, str(custom) + '\n')
        os.write(ttt, str(cleanup) + '\n')
        os.write(ttt, str(ccjobs) + '\n')

        if nvidia:
            nv_version, nv_url, nv_file = self.nv_get_latest()
            os.write(ttt, nv_version + '\n')
            os.write(ttt, nv_url + '\n')
            os.write(ttt, nv_file + '\n')

        # always close and remove the temp file
        # before you exit from script
        os.close(ttt)

        return t_path

    # When the user wants to build the kernel, run this
    def build(self, widget):

        ## Added for debugging purposes
        kcd = KCDebug(False)

        # Put the data in an array
        data_path = sys.argv[1]
        data_file = open(data_path, 'r')
        data1 = data_file.read()
        data_file.close()
        data = eval(data1)
        os.remove(data_path)

        ## Added for debugging purposes
        kcd.add_debug_msg(__file__, 'data', data)

        # Get the temporary file where the KC settings are stored
        self.error = False
        self.buildlock = True
        self.closeallowed(False)
        gtk.main_iteration()

        # Get data from tuple
        stable = data[0]
        stable_url = data[1]
        patch = data[2]
        patch_url = data[3]
        patchtype = data[4]
        config = data[5]
        alsa = data[6]
        optimize = data[7]
        reconfigure = data[8]
        nvidia = data[9]
        fglrx = data[10]
        installpackage = data[11]
        custom = data[12]
        cleanup = data[13]
        ccjobs = data[14]

        # Get a temporary file
        temp_path = self.get_temp_path(data)

        ## Added for debugging purposes
        kcd.add_debug_msg(__file__, 'config', config)
        kcd.add_debug_msg(__file__, 'alsa', alsa)
        kcd.add_debug_msg(__file__, 'optimize', optimize)
        kcd.add_debug_msg(__file__, 'reconfigure', reconfigure)
        kcd.add_debug_msg(__file__, 'nvidia', nvidia)
        kcd.add_debug_msg(__file__, 'fglrx', fglrx)
        kcd.add_debug_msg(__file__, 'patchtype', patchtype)
        kcd.add_debug_msg(__file__, 'data', data)
        kcd.add_debug_msg(__file__, 'installpackage', installpackage)
        kcd.add_debug_msg(__file__, 'custom', custom)
        kcd.add_debug_msg(__file__, 'cleanup', cleanup)
        kcd.add_debug_msg(__file__, 'ccjobs', ccjobs)
        kcd.add_debug_msg(__file__, 'temp_path', temp_path)

#        # Run the various stages of the shell script
        gtk.main_iteration()
        self.ProgressBar.set_fraction(0.04)
        self.lblMessage.set_text("Running prestages")
        self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh prestage '+str(temp_path)+'\n')
        self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.1)
            self.lblMessage.set_text("Установка зависимостей....")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage1 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.2)
            self.lblMessage.set_text("Скачиваем пакеты")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage2 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.3)
            self.lblMessage.set_text("Извлечение пакетов")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage3 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error and (custom == True):
            gtk.main_iteration()
            self.lblMessage.set_text("Применение обычного выбранного набора патчей")
            from KernelCheck import virtualterminal
            virtualterminal.console()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.4)
            self.lblMessage.set_text("конфигурирование ядра")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage4 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.5)
            self.lblMessage.set_text("очистка перед построением ядра")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage5 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.7)
            self.lblMessage.set_text("Построение ядра: процесс займет примерно 2-4 часа")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage6 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error and installpackage == True:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.9)
            self.lblMessage.set_text("Завершаем процесс: Инсталлируем новое ядро как deb пакет")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage7 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error and nvidia == True:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.93)
            self.lblMessage.set_text("Завершаем процесс: Инсталлируем модуль ядра от nVidia ")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh nvidia '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error and cleanup == True:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(0.98)
            self.lblMessage.set_text("Чистим исходники ядра")
            process = self.run_command('bash ' + KCHECK_SCRIPTS + 'kscript.sh stage8 '+str(temp_path)+'\n')
            self.checkerror()

        if not self.error:
            gtk.main_iteration()
            self.ProgressBar.set_fraction(1.0)
            self.lblMessage.set_text("Процесс завершен")
            self.closeallowed(True)
            self.buildlock = False

        return

    def help(self,widget):
        import webbrowser
        webbrowser.open('http://kcheck.sourceforge.net/pool/Documentation.pdf')

    # Response events for destroy_window
    def close2(self, w, res):
        if res == gtk.RESPONSE_YES:
            self.run_command_done_callback(self.Terminal)
            self.run_command('echo \nВы можете закрыть программу.')
            self.error = True
            self.btnQuit.set_sensitive(True)
            self.buildlock = False
            self.run_command_done_callback(self.Terminal)
            w.hide()
            return
        elif res == gtk.RESPONSE_NO:
            w.hide()
            return

    # Destroy the window
    def destroy_window(self, widget):
        if self.buildlock == True:
            error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, 'Идет построение ядра! Крайне нежелательно прерывать данный процесс. Вы уверены что хотите выйти из программы?')
            error.connect("response", self.close2)
            error.run()
        else:
            self.run_command_done_callback(self.Terminal)
            self.error = True
            gtk.main_quit()

window = KernelCheck()
gtk.main()
